﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace Borg
{
    public class Initializer : InitializerBase
    {
        public Runtime CreateRuntime()
        {
            return InternalCreateRuntime();
        }

        public Runtime CreateRuntime(out RuntimeInternals runtimeInternals)
        {
            return InternalCreateRuntime(out runtimeInternals);
        }

        public Runtime CreateAndStartRuntime()
        {
            var runtime = InternalCreateRuntime();
            
            if (runtime.Dispatcher.CheckAccess())
                InternalStart(runtime);
            else
                runtime.Dispatcher.Invoke(new Action<Runtime>(InternalStart), new object[] { runtime });

            return runtime;
        }

        public void Start(Runtime runtime)
        {
            if (runtime == null)
                throw new ArgumentNullException("runtime");

            if (runtime.Dispatcher.CheckAccess())
                InternalStart(runtime);
            else
                runtime.Dispatcher.Invoke(new Action<Runtime>(InternalStart), new object[] { runtime });
        }

        private void InternalStart(Runtime runtime)
        {
            if (runtime.IsRunning)
                throw new InvalidOperationException(
                    "Error on starting runtime. Runtime is already running.");
            
            if (runtime.Dispatcher.IsDisposed)
                throw new InvalidOperationException(
                    "Error on starting runtime. Runtime dispatcher is already disposed.");
            
            runtime.Start(() => GetStartupContractsAndStartupPlugins(runtime));
        }

        private Tuple<IEnumerable<Contract>, IEnumerable<Plugin>> GetStartupContractsAndStartupPlugins(Runtime runtime)
        {
            List<Contract> contracts;
            List<Plugin> plugins;

            AnalyzeStartupPaths(runtime, out contracts, out plugins);
            
            return new Tuple<IEnumerable<Contract>, IEnumerable<Plugin>>(contracts, plugins);
        }

        private Runtime InternalCreateRuntime(out RuntimeInternals runtimeInternals)
        {
            return base.InternalCreateRuntime(GetDefaultUser(), out runtimeInternals);
        }

        private Runtime InternalCreateRuntime()
        {
            return base.InternalCreateRuntime(GetDefaultUser());
        }

        protected virtual string GetDefaultUser()
        {
            return Environment.UserName;
        }

        protected override string[] GetLoaderDirectories(string user, List<LogArgs> logs)
        {
            if (loaderPaths.Count == 0)
                return base.GetLoaderDirectories(user, logs);

            var orderedSet = new OrderedSet<string>();
            
            foreach (var path in loaderPaths)
            {
                var directories = PathHelper.GetDirectories(path);

                if (directories == null)
                {
                    logs.Add(
                        new LogArgs(
                            LogType.Warning,
                            this,
                            "Loader path not found: '{0}'",
                            new object[] {path}));
                }
                else
                {
                    foreach (var directory in directories)
                        orderedSet.Add(directory);
                }
            }

            return orderedSet.ToArray();
        }

        protected virtual void AnalyzeStartupPaths(Runtime runtime, out List<Contract> contracts, out List<Plugin> plugins)
        {
            new AssemblyAnalyzer(runtime).Analyze(GetStartupUris(runtime), out contracts, out plugins);
        }

        protected virtual Uri[] GetStartupUris(Runtime runtime)
        {
            var orderedSet = new OrderedSet<Uri>();
            var execUri = new Uri(Assembly.GetExecutingAssembly().EscapedCodeBase);
            IEnumerable<string> enumerable;
            
            if (startupPaths.Count <= 0)
                enumerable = new[] { "**" };
            else
                enumerable = startupPaths;

            foreach (var path in enumerable)
            {
                var files = PathHelper.GetFiles(path);

                if (files == null)
                    runtime.Logger.Log(
                        new LogArgs(
                            LogType.Warning,
                            this,
                            "Startup path not found: '{0}'",
                            new object[] {path}));
                else
                    foreach (var uri in files.Select(
                        file => new Uri(file)).Where(uri => !uri.Equals(execUri)))
                        orderedSet.Add(uri);
            }

            return orderedSet.ToArray();
        }
    }
}
